/*
 * Copyright (c) 2009, 2012 IBM Corp.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Dave Locke - initial API and implementation and/or initial documentation
 */
package org.fish.chat.mqtt.protocol;

/**
 * An MQTT message holds the application payload and options specifying how
 * the message is to be delivered The message includes a "payload" (the
 * body of the message) represented as a byte[].
 */
public class MqttMessage implements java.io.Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = -4643986642017095415L;

    public static final int QOS_AT_MOST_ONCE = 0;

    public static final int QOS_AT_LEAST_ONCE = 1;

    public static final int QOS_EXACTLY_ONCE = 2;

    private boolean mutable = true;

    private byte[] payload;

    private int qos = 1;

    private boolean retained = false;

    private boolean dup = false;

    /**
     * Utility method to validate the supplied QoS value.
     * 
     * @throws IllegalArgumentException if value of QoS is not 0, 1 or 2.
     */
    public static void validateQos(int qos) {
        if ((qos < 0) || (qos > 2)) {
            throw new IllegalArgumentException();
        }
    }

    /**
     * Constructs a message with an empty payload, and all other values set
     * to defaults.
     * 
     * The defaults are:
     * <ul>
     * <li>Message QoS set to 1</li>
     * <li>Message will not be "retained" by the server</li>
     * </ul>
     */
    public MqttMessage() {
        setPayload(new byte[] {});
    }

    /**
     * Constructs a message with the specified byte array as a payload, and
     * all other values set to defaults.
     */
    public MqttMessage(byte[] payload) {
        setPayload(payload);
    }

    /**
     * Returns the payload as a byte array.
     * 
     * @return the payload as a byte array.
     */
    public byte[] getPayload() {
        return payload;
    }

    /**
     * Clears the payload, resetting it to be empty.
     * 
     * @throws IllegalStateException if this message cannot be edited
     */
    public void clearPayload() {
        checkMutable();
        this.payload = new byte[] {};
    }

    /**
     * Sets the payload of this message to be the specified byte array.
     * 
     * @param payload the payload for this message.
     * @throws IllegalStateException if this message cannot be edited
     * @throws NullPointerException if no payload is provided
     */
    public void setPayload(byte[] payload) {
        checkMutable();
        if (payload == null) {
            throw new NullPointerException();
        }
        this.payload = payload;
    }

    /**
     * Returns whether or not this message should be/was retained by the
     * server. For messages received from the server, this method returns
     * whether or not the message was from a current publisher, or was
     * "retained" by the server as the last message published on the topic.
     * 
     * @return <code>true</code> if the message should be, or was, retained
     *         by the server.
     * @see #setRetained(boolean)
     */
    public boolean isRetained() {
        return retained;
    }

    /**
     * Whether or not the publish message should be retained by the
     * messaging engine. Sending a message with the retained set to
     * <code>false</code> will clear the retained message from the server.
     * The default value is <code>false</code>
     * 
     * @param retained whether or not the messaging engine should retain
     *        the message.
     * @throws IllegalStateException if this message cannot be edited
     */
    public void setRetained(boolean retained) {
        checkMutable();
        this.retained = retained;
    }

    /**
     * Returns the quality of service for this message.
     * 
     * @return the quality of service to use, either 0, 1, or 2.
     * @see #setQos(int)
     */
    public int getQos() {
        return qos;
    }

    /**
     * Sets the quality of service for this message.
     * <ul>
     * <li>Quality of Service 0 - indicates that a message should be
     * delivered at most once (zero or one times). The message will not be
     * persisted to disk, and will not be acknowledged across the network.
     * This QoS is the fastest, but should only be used for messages which
     * are not valuable - note that if the server cannot process the
     * message (for example, there is an authorization problem), then an
     * {@link MqttCallback#deliveryComplete(IMqttDeliveryToken)}. Also
     * known as "fire and forget".</li>
     * 
     * <li>Quality of Service 1 - indicates that a message should be
     * delivered at least once (one or more times). The message can only be
     * delivered safely if it can be persisted, so the application must
     * supply a means of persistence using <code>MqttConnectOptions</code>.
     * If a persistence mechanism is not specified, the message will not be
     * delivered in the event of a client failure. The message will be
     * acknowledged across the network. This is the default QoS.</li>
     * 
     * <li>Quality of Service 2 - indicates that a message should be
     * delivered once. The message will be persisted to disk, and will be
     * subject to a two-phase acknowledgement across the network. The
     * message can only be delivered safely if it can be persisted, so the
     * application must supply a means of persistence using
     * <code>MqttConnectOptions</code>. If a persistence mechanism is not
     * specified, the message will not be delivered in the event of a
     * client failure.</li>
     * 
     * If persistence is not configured, QoS 1 and 2 messages will still be
     * delivered in the event of a network or server problem as the client
     * will hold state in memory. If the MQTT client is shutdown or fails
     * and persistence is not configured then delivery of QoS 1 and 2
     * messages can not be maintained as client-side state will be lost.
     * 
     * @param qos the "quality of service" to use. Set to 0, 1, 2.
     * @throws IllegalArgumentException if value of QoS is not 0, 1 or 2.
     * @throws IllegalStateException if this message cannot be edited
     */
    public void setQos(int qos) {
        checkMutable();
        validateQos(qos);
        this.qos = qos;
    }

    /**
     * Returns a string representation of this message's payload. Makes an
     * attempt to return the payload as a string. As the MQTT client has no
     * control over the content of the payload it may fail.
     * 
     * @return a string representation of this message.
     */
    public String toString() {
        return "";
    }

    /**
     * Sets the mutability of this object (whether or not its values can be
     * changed.
     * 
     * @param mutable <code>true</code> if the values can be changed,
     *        <code>false</code> to prevent them from being changed.
     */
    protected void setMutable(boolean mutable) {
        this.mutable = mutable;
    }

    protected void checkMutable() throws IllegalStateException {
        if (!mutable) {
            throw new IllegalStateException();
        }
    }

    protected void setDuplicate(boolean dup) {
        this.dup = dup;
    }

    /**
     * Returns whether or not this message might be a duplicate of one
     * which has already been received. This will only be set on messages
     * received from the server.
     * 
     * @return <code>true</code> if the message might be a duplicate.
     */
    public boolean isDuplicate() {
        return this.dup;
    }
}
